﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace WSU.ChekaParallel.TestTask
{
    static class TestContinueWithError
    {
        private static void Print(this Exception ex)
        {
            Console.WriteLine("!!! Exception Caught. Source='{0}',Message='{1}'", ex.Source, ex.Message);
        }

        private static IEnumerable<Exception> RetrieveExceptions(this IEnumerable<Task> antecedents)
        {
            return from antecedent in antecedents
                   where antecedent.IsFaulted
                   from ex in antecedent.Exception.Flatten().InnerExceptions
                   select ex;
        }

        /// <summary>
        /// ignoring antecedent's status (whether it meets a fault) will cause that exceptions unhandled
        /// which will possibly crash the whole application when that faulted task is GCed. 
        /// </summary>
        private static void IgnoreAntecedentException()
        {
            Task task1 = Task.Factory.StartNew(() => Console.WriteLine("task1 runs"));
            Task task2 = task1.ContinueWith(antecedent =>
                                                {
                                                    Console.WriteLine("task2 runs, but throws an exception");
                                                    throw new InvalidOperationException("test");
                                                });
            Task task3 = task2.ContinueWith(antecedent => Console.WriteLine("task3 runs by ignoring antecedent's status"));

            task3.Wait();

            Helper.Pause();
        }

        private static void CheckAntecedentExceptions()
        {
            Task task1 = Task.Factory.StartNew(() => Console.WriteLine("task1 runs"));
            Task task2 = task1.ContinueWith(antecedent =>
            {
                Console.WriteLine("task2 runs, but throws an exception");
                throw new InvalidOperationException("test") { Source = "task2" };
            });
            Task task3 = task2.ContinueWith(antecedent =>
                                                {
                                                    if (antecedent.IsFaulted)
                                                    {
                                                        Console.WriteLine("task3: antecedent has fault, rethrow the exception");
                                                        throw antecedent.Exception.InnerException;
                                                    }
                                                    else
                                                        Console.WriteLine("task3 runs");
                                                });

            try
            {
                task3.Wait();
            }
            catch (AggregateException aggregateError)
            {
                foreach (Exception ex in aggregateError.Flatten().InnerExceptions)
                {
                    ex.Print();
                }
            }


            Helper.Pause();
        }

        private static void CheckAllAntecedents(bool hasFault)
        {
            Task task1 = Task.Factory.StartNew(() => Console.WriteLine("task1 runs"));
            Task task2 = Task.Factory.StartNew(() =>
                                                   {
                                                       if (hasFault)
                                                           throw new InvalidOperationException("test") { Source = "task2" };
                                                       else
                                                           Console.WriteLine("task2 runs");
                                                   });

            Task<int> allCompleted = Task.Factory.ContinueWhenAll(
                new Task[] { task1, task2 },
                antecedents =>
                {
                    Exception[] previousExceptions = antecedents.RetrieveExceptions().ToArray();
                    if (previousExceptions.Length == 0)
                        return 100;
                    else
                    {
                        throw new AggregateException(previousExceptions);
                        return -1;
                    }
                });

            try
            {
                Console.WriteLine("final result={0}", allCompleted.Result);
            }
            catch (AggregateException aggregatedError)
            {
                // chekanote: Flatten here is necessary, otherwise, "ex" will still be "AggregateException"
                foreach (Exception ex in aggregatedError.Flatten().InnerExceptions)
                {
                    ex.Print();
                }
            }
        }

        /// <summary>
        /// the continuation will run no matter how the first-completed task completes
        /// </summary>
        private static void ContinueAny_ContinueOptions(TaskContinuationOptions continueOption)
        {
            // ----------------------------- make source tasks
            Task[] tasks = new Task[2];

            tasks[0] = new Task(() =>
            {
                Thread.Sleep(2000);
                Console.WriteLine("OK.");
            });

            CancellationTokenSource cts = new CancellationTokenSource();
            tasks[1] = new Task(() =>
            {
                cts.Token.WaitHandle.WaitOne(500);
                Console.WriteLine("Canceled.");
                cts.Token.ThrowIfCancellationRequested();
            }, cts.Token);

            // ----------------------------- make continuation task
            Task continuation = Task.Factory.ContinueWhenAny(
                tasks,
                firstCompleted => Console.WriteLine("!!! first completed status={0}", firstCompleted.Status),
                continueOption);

            // ----------------------------- start
            foreach (Task task in tasks)
            {
                task.Start();
            }
            cts.Cancel();

            Helper.Pause();
        }

        private static void BestErrorPolicy4ContinueAny()
        {
            // ---------------------------------- source tasks
            Task[] tasks = new Task[2];
            tasks[0] = new Task(() =>
                                    {
                                        Thread.Sleep(1000);
                                        Console.WriteLine("ok");
                                    });
            tasks[1] = new Task(() =>
                                    {
                                        Thread.Sleep(2000);
                                        throw new InvalidOperationException("test") { Source = "error-task" };
                                    });

            // ---------------------------------- continuation when first completes
            Task continuation = Task.Factory.ContinueWhenAny(
                tasks, firstCompleted => Console.WriteLine("first compleeted task's status={0}", firstCompleted.Status));

            // ---------------------------------- start
            foreach (Task task in tasks)
            {
                task.Start();
            }

            // ---------------------------------- to deal with any possible error
            // avoid those error becomes unhandled, which will crash the whole application when being GCed.
            Task errorDealer = Task.Factory.ContinueWhenAll(tasks,
                antecedents =>
                {
                    Exception[] exceptions = antecedents.RetrieveExceptions().ToArray();
                    Console.WriteLine("all {0} tasks completes with {1} exceptions: ", antecedents.Length, exceptions.Length);
                    foreach (Exception ex in exceptions)
                    {
                        ex.Print();
                    }
                });

            Helper.Pause();
        }

        public static void TestMain()
        {
            // IgnoreAntecedentException();
            // CheckAntecedentExceptions();
            // CheckAllAntecedents(false);
            // CheckAllAntecedents(true);
            // ContinueAny_ContinueOptions(TaskContinuationOptions.None);
            // !!!!!!! chekanote: actually, we cannot control how to fire the continuation task
            // !!!!!!! for example, if the first completed is canceled, we won't fire the continuation
            // !!!!!!! we want to wait for the first non-canceled, non-faulted completed one
            // !!!!!!! we cannot achieve such purpose by specifying "NotOnXXX" or "OnlyOnXXX" continuation options
            // !!!!!!! those options are forbidden when using "ContinueAny"
            // ContinueAny_ContinueOptions(TaskContinuationOptions.NotOnCanceled);
            BestErrorPolicy4ContinueAny();
        }
    }
}
